import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * Generalized class for Markov Models. Makes a call to MarkovIO to initialize
 * and the Markov model information, and makes calls to the MarkovIO to obtain 
 * model info (PTables, etc). This information is then sorted and/or sampled to
 * return suggestions. This class provides the API for the generalized Markov
 * Model.
 */
public class MarkovModel
{
	public static final int VERBOSITY = 3;
	private MarkovStateMgr stateMgr;
	private MarkovStateSet currentPriors;
	private MarkovIO markovData;
	private int order;
	
	/**
	 * Constructor for the <code>MarkovModel</code> class. Returns a 
	 * MarkovModel with the specified order.
	 * 
	 * @param order <code>int</code>
	 * @param loadFilePath <code>String</code> Path to the existing markov db
	 */
	public MarkovModel(int order, String loadFilePath)
	{
		if(VERBOSITY == 3) 
			System.out.println("ENTER: MarkovModel()");
		this.stateMgr = MarkovStateMgr.getSingleton();
		
		this.currentPriors = new MarkovStateSet();
		this.markovData = new MarkovIOTree(loadFilePath);
		this.order = order;
		
		if(VERBOSITY == 3) 
			System.out.println("EXIT: MarkovModel");
	}
	
	/**
	 * Clear the prior states to start fresh. Useful when we want to ignore
	 * all prior state transitions (for example, when we encounter a newline).
	 */
	public void clearState()
	{
		if(VERBOSITY == 3) 
			System.out.println("ENTER: MarkovModel.clearState()");
		currentPriors.clear();
		if(VERBOSITY == 3)
			System.out.println("EXIT: MarkovModel.clearState");	
	}

	/**
	 * Get the order of this MarkovModel.
	 * 
	 * @return <code>int</code>
	 */
	public int getOrder()
	{
		if(VERBOSITY == 3) 
			System.out.println("ENTER: MarkovModel.getOrder()");
		
		if(VERBOSITY == 3) 
			System.out.println("EXIT: MarkovModel.getOrder(order = " + order + ")");
		return order;
	}
	/**
	 * Return a *SAFE* copy of the ordered predictions. Modification-ready.
	 * 
	 * @param states <code>Object[]</code>
	 * @return <code>Object[]</code>
	 */
	public Object[] getOrderedPredictions(Object[] tokens)
	{
		if(VERBOSITY == 3) 
			System.out.println("ENTER: MarkovModel.getOrderedPredictions(" +
					"tokens = " + tokens + ")");
		
		MarkovStateSet stateSet = stateMgr.getMarkovStateSet(tokens);
		MarkovPTable ptable = markovData.getPTable(stateSet);
		
		List<Object> result = new LinkedList<Object>();
		for(MarkovPTableEntry entry : ptable.getMarkovPTableEntries())
		{
			result.add(entry.getState().getToken());
		}
		
		if(VERBOSITY == 3) 
			System.out.println("EXIT: MarkovModel.getOrderedPredictions");
		
		return result.toArray();
	}
	
	/**
	 * Return a *SAFE* copy of the ordered <code>MarkovPTable</code>s. 
	 * Modification-ready.
	 * 
	 * @param tokens <code>Object[]</code>
	 * @return <code>MarkovPTable</code>
	 */
	public MarkovPTable getOrderedPTable(Object[] tokens)
	{
		if(VERBOSITY == 3) 
			System.out.println("ENTER: MarkovModel.getOrderedPTable(" +
					"tokens = " + tokens + ")");
		
		// TODO: Is this safe?
		MarkovStateSet stateSet = stateMgr.getMarkovStateSet(tokens);
		MarkovPTable ptable = new MarkovPTable(markovData.getPTable(stateSet));
		
		if(VERBOSITY == 3) 
			System.out.println("EXIT: MarkovModel.getOrderedPTable");
		
		return ptable;
	}
	
	/**
	 * Used for training the <code>MarkovModel</code> object. For this 
	 * MarkovModel to work, newState must be called whenever a state transition
	 * (i.e. new character or word) occurs.
	 * 
	 * @param newStateObj <code>Object</code>
	 */
	public void newState(Object newStateObj)
	{
		if(VERBOSITY == 3) 
			System.out.println("ENTER: MarkovModel.newState(" +
					"newStateObj = " + newStateObj + ")");
		
		// Get the MarkovState from the Mgr
		MarkovState state = stateMgr.getMarkovState(newStateObj);
		
		// Retrieve probability table and tell it the state was elected
		MarkovPTable ptable = 
			markovData.getPTable(currentPriors);
		ptable.addInstance(state);
		
		// Update the markovData (DB)
		markovData.setPTable(currentPriors, ptable);
		

		// Update the current state (Current state is the most recent)
		currentPriors.push(state);
		
		// Pop off states to ensure that we only have order# of priors
		while(currentPriors.size() > order)
			currentPriors.pop();
		
		
		if(VERBOSITY == 3) 
			System.out.println("EXIT: MarkovModel.newState");
	}
	
	public void printMarkovModel()
	{
		if(VERBOSITY == 3) 
			System.out.println("ENTER: MarkovModel.printMarkovModel()");
			
		System.out.println("VERBOSITY = " + VERBOSITY);
		System.out.println("stateMgr = " + stateMgr);
		System.out.println("currentPriors = " + currentPriors);
		System.out.println("markovData = " + markovData);
		System.out.println("order = " + order);
				
		if(VERBOSITY == 3) 
			System.out.println("EXIT: MarkovModel.printMarkovModel");
	}
}
